import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { RequestBookHandler, RequestGroupBase } from './requestGroups/RequestGroupBase';
import { CNBlogsGroup } from './requestGroups/cnblogsGroup';
import { ExtensionContext, window, commands, ViewColumn } from 'vscode';
import { getBookChapters, getBookContent, mkdirs, webRequest } from './lib/RequestUtil';
import { ICatchConfig, ICatchDetailConfig } from './lib/config';
import { Book } from './bookUtil';

let extensionPath = '';

export function registerRequestGroupProvider(context: ExtensionContext, book: Book) {
    extensionPath = context.extensionPath;

    let provider = new RequestGroupProvider();
    window.registerTreeDataProvider('requestGroup', provider);

    let detailProvider = new RequestDetailProvider();
    window.registerTreeDataProvider('fishBookView', detailProvider);

    commands.registerCommand('requestGroup.addEntry', () => {
        showEditView();
    });

    commands.registerCommand('requestGroup.editEntry', (element: GroupTreeItem) => {
        showEditView(true, element.config);
    });

    commands.registerCommand('requestGroup.deleteEntry', (element: GroupTreeItem) => {
        let absoluteName = getConfigJsonPath(element?.config?.name);
        fs.unlinkSync(absoluteName);
        provider.refresh();
    });

    commands.registerCommand('requestGroup.refreshEntry', () => {
        provider.refresh();
    });

    function showEditView(isEditing: boolean = false, editData?: ICatchConfig) {
        // Create and show a new webview
        const panel = window.createWebviewPanel(
            'RequestGroupConfig', // Identifies the type of the webview. Used internally
            `RequestGroupConfig[${isEditing ? 'Edit' : 'Add'}]`, // Title of the panel displayed to the user
            ViewColumn.One, // Editor column to show the new webview panel in.
            {
                enableScripts: true,
                retainContextWhenHidden: true
            } // Webview options. More on these later.
        );

        let path = `${context.extensionPath}/public/groupsetting.html`;
        let content = fs.readFileSync(path, { encoding: 'utf-8' });
        content = content.replace('"%Config%"', JSON.stringify({
            isEditing, editData
        }, undefined, 2));

        panel.webview.html = content;

        panel.webview.onDidReceiveMessage(
            message => {
                switch (message.command) {
                    case 'getHtmlContentFromUrl':
                        getHtmlContentFromUrl(message.text);
                        return;
                    case 'getHtmlContentFromDetailUrl':
                        getHtmlContentFromDetailUrl(message.text);
                        break;
                    case 'catchTest':
                        let data = JSON.parse(message.text);
                        catchTest(data.url, data.htmlContent, data.start, data.end, data.regex);
                        return;
                    case 'catchDetailTest':
                        let data2 = message.data;
                        catchDetailTest(data2.htmlContent, data2.regex);
                        break;
                    case 'saveConfig':
                        saveConfig(message.data);
                        break;
                }
            },
            undefined,
            context.subscriptions
        );

        function getHtmlContentFromUrl(url: string) {
            webRequest(url).then(content => {
                panel.webview.postMessage({
                    command: 'getHtmlContentFromUrlResult',
                    text: content
                });
            });
        }

        function catchTest(url: string, htmlContent: string, start: string, end: string, regexStr: string) {
            let list = getBookChapters(url, htmlContent, start, end, regexStr);
            panel.webview.postMessage({
                command: 'catchTestResult',
                data: list
            });
        }

        function catchDetailTest(htmlContent: string, regexStr: string) {
            let str = getBookContent(htmlContent, regexStr);
            panel.webview.postMessage({
                command: 'catchDetailTestResult',
                data: str
            });
        }

        function getHtmlContentFromDetailUrl(url: string) {
            webRequest(url).then(content => {
                panel.webview.postMessage({
                    command: 'getHtmlContentFromDetailUrlResult',
                    text: content
                });
            });
        }

        function saveConfig(data: ICatchConfig) {
            mkdirs(`${context.extensionPath}/catch/group`);
            fs.writeFileSync(getConfigJsonPath(data.name), JSON.stringify(data, undefined, 2), { encoding: 'utf-8' });
            window.showInformationMessage("save success");
            provider.refresh();
        }
    }

    commands.registerCommand('fishBookView.addEntry', () => {
        provider.getChildren().then(list => {
            let names = list.map(a => a.config.name);
            showEditDetailView(names);
        });
    });

    commands.registerCommand('fishBookView.editEntry', (element: DetailTreeItem) => {
        provider.getChildren().then(list => {
            let names = list.map(a => a.config.name);
            showEditDetailView(names, true, element.config);
        });
    });

    commands.registerCommand('fishBookView.deleteEntry', (element: DetailTreeItem) => {
        let absoluteName = getDetailConfigJsonPath(element?.config?.name);
        fs.unlinkSync(absoluteName);
        detailProvider.refresh();
    });

    commands.registerCommand('fishBookView.refreshEntry', () => {
        detailProvider.refresh();
    });

    function showEditDetailView(requestGroups: string[], isEditing: boolean = false, editData?: ICatchDetailConfig) {
        // Create and show a new webview
        const panel = window.createWebviewPanel(
            'RequestDetailConfig', // Identifies the type of the webview. Used internally
            `RequestDetailConfig[${isEditing ? 'Edit' : 'Add'}]`, // Title of the panel displayed to the user
            ViewColumn.One, // Editor column to show the new webview panel in.
            {
                enableScripts: true,
                retainContextWhenHidden: true
            } // Webview options. More on these later.
        );

        let path = `${context.extensionPath}/public/detailsetting.html`;
        let content = fs.readFileSync(path, { encoding: 'utf-8' });
        content = content.replace('"%Config%"', JSON.stringify({
            requestGroups, isEditing, editData
        }, undefined, 2));

        panel.webview.html = content;

        panel.webview.onDidReceiveMessage(
            message => {
                switch (message.command) {
                    case 'saveConfig':
                        saveConfig(message.data);
                        break;
                    case 'loadData':
                        loadData(message.data);
                        break;
                }
            },
            undefined,
            context.subscriptions
        );

        function saveConfig(data: ICatchDetailConfig) {
            mkdirs(`${context.extensionPath}/catch/detail`);
            fs.writeFileSync(getDetailConfigJsonPath(data.name), JSON.stringify(data, undefined, 2), { encoding: 'utf-8' });
            window.showInformationMessage("save success");
            detailProvider.refresh();
        }

        function loadData(data: ICatchDetailConfig) {
            let jsonData = getConfigJson(data.requestGroupName);
            let handler = new RequestBookHandler(jsonData, data, extensionPath);
            handler.loadData();

            handler.loadingProcessEvent = (index, total) => {
                panel.webview.postMessage({
                    command: 'loadingResult',
                    data: { index, total }
                });
            };
        }
    }

    commands.registerCommand('fishBookView.viewDetailEntry', (element: DetailTreeItem) => {
        let handler = new RequestBookHandler({} as any, element.config, extensionPath);
        let filePath = `${handler.getContentDir()}/${element.bookSortId}.txt`;
        if (!fs.existsSync(filePath)) {
            window.showErrorMessage('Load file fail\n' + filePath);
            return;
        }
        vscode.workspace.openTextDocument(filePath).then(doc => {
            vscode.window.showTextDocument(doc);
        });
    });

    commands.registerCommand('fishBookView.startView', (element: DetailTreeItem) => {
        let handler = new RequestBookHandler({} as any, element.config, extensionPath);
        let filePath = `${handler.getContentDir()}/${element.bookSortId}.txt`;
        if (!fs.existsSync(filePath)) {
            window.showErrorMessage('Load file fail\n' + filePath);
            return;
        }

        book.setNewBookStatus(filePath);
    });
}

export class RequestGroupProvider implements vscode.TreeDataProvider<GroupTreeItem> {
    private _onDidChangeTreeData: vscode.EventEmitter<GroupTreeItem | undefined | null | void> = new vscode.EventEmitter<GroupTreeItem | undefined | null | void>();
    readonly onDidChangeTreeData: vscode.Event<GroupTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;

    requestGroups: RequestGroupBase[] = [new CNBlogsGroup()];

    constructor() {
    }

    refresh(): void {
        this._onDidChangeTreeData.fire();
    }

    getTreeItem(element: GroupTreeItem): vscode.TreeItem {
        return element;
    }

    getChildren(element?: GroupTreeItem): Thenable<GroupTreeItem[]> {
        let dirPath = `${extensionPath}/catch/group`;
        if (!fs.existsSync(dirPath)) {
            return Promise.resolve([]);
        }
        let dirs = fs.readdirSync(dirPath);
        let list: ICatchConfig[] = dirs.map(a => {
            let jsonStr = fs.readFileSync(`${dirPath}/${a}`, { encoding: 'utf-8' });
            let data = JSON.parse(jsonStr);
            return data;
        });

        let trees = list.map(a => new GroupTreeItem(a));
        return Promise.resolve(trees);
    }
}

export class RequestDetailProvider implements vscode.TreeDataProvider<DetailTreeItem> {
    private _onDidChangeTreeData: vscode.EventEmitter<DetailTreeItem | undefined | null | void> = new vscode.EventEmitter<DetailTreeItem | undefined | null | void>();
    readonly onDidChangeTreeData: vscode.Event<DetailTreeItem | undefined | null | void> = this._onDidChangeTreeData.event;

    requestGroups: RequestGroupBase[] = [new CNBlogsGroup()];

    constructor() {
    }

    refresh(): void {
        this._onDidChangeTreeData.fire();
    }

    getTreeItem(element: DetailTreeItem): vscode.TreeItem {
        return element;
    }

    getChildren(element?: DetailTreeItem): Thenable<DetailTreeItem[]> {
        if (!element) {
            let tree = this.getRootChildren();
            return Promise.resolve(tree);
        } else if (element.nodeType === ETreeDetail.book) {
            let tree = this.getBatchChildren(element);
            return Promise.resolve(tree);
        } else if (element.nodeType === ETreeDetail.batch) {
            let tree = this.getDetailChildren(element);
            return Promise.resolve(tree);
        }

        return Promise.resolve([]);
    }

    private getRootChildren(): DetailTreeItem[] {
        let dirPath = `${extensionPath}/catch/detail`;
        if (!fs.existsSync(dirPath)) {
            return [];
        }
        let dirs = fs.readdirSync(dirPath);
        let list: ICatchDetailConfig[] = dirs.map(a => {
            let jsonStr = fs.readFileSync(`${dirPath}/${a}`, { encoding: 'utf-8' });
            let data = JSON.parse(jsonStr);
            return data;
        });

        let trees = list.map(a => new DetailTreeItem(a, ETreeDetail.book, vscode.TreeItemCollapsibleState.Collapsed));
        return trees;
    }

    private getBatchChildren(treeItem: DetailTreeItem): DetailTreeItem[] {
        let handler = new RequestBookHandler({} as any, treeItem.config, extensionPath);
        let chapters = handler.getProcessJson();

        let batchSize = 50;
        let count = Math.ceil(chapters.length / batchSize);
        let list: DetailTreeItem[] = [];
        for (let index = 0; index < count; index++) {
            let item = new DetailTreeItem(treeItem.config, ETreeDetail.batch, vscode.TreeItemCollapsibleState.Collapsed);
            let endStr = Math.min(chapters.length, (index + 1) * batchSize);
            item.label = `${1 + index * batchSize}-${endStr}`;
            item.batchIndex = index;
            item.batchSize = batchSize;
            list.push(item);
        }
        return list;
    }

    private getDetailChildren(treeItem: DetailTreeItem): DetailTreeItem[] {
        let handler = new RequestBookHandler({} as any, treeItem.config, extensionPath);
        let chapters = handler.getProcessJson();

        chapters = chapters.splice(treeItem.batchIndex * treeItem.batchSize, treeItem.batchSize);
        let tree = chapters.map(a => {
            let item = new DetailTreeItem(treeItem.config, ETreeDetail.detail);
            item.label = `[${a.sortId}] ${a.title}`;
            item.bookSortId = a.sortId!;
            return item;
        });
        return tree;
    }
}

class GroupTreeItem extends vscode.TreeItem {
    constructor(
        public readonly config: ICatchConfig
    ) {
        super(config.name);
    }
}

class DetailTreeItem extends vscode.TreeItem {
    batchIndex = 0;
    batchSize = 50;

    bookSortId = 0;
    constructor(
        public readonly config: ICatchDetailConfig,
        public nodeType: ETreeDetail,
        public readonly collapsibleState?: vscode.TreeItemCollapsibleState
    ) {
        super(config.name, collapsibleState);

        switch (nodeType) {
            case ETreeDetail.book:
                this.contextValue = 'book';
                this.iconPath = `${extensionPath}/media/book.svg`;
                break;
            case ETreeDetail.batch:
                this.iconPath = `${extensionPath}/media/batch.svg`;
                break;
            case ETreeDetail.detail:
                this.contextValue = 'detail';
                break;
        }
    }
}

enum ETreeDetail {
    book,
    batch,
    detail
}

function getConfigJsonPath(configName: string): string {
    let path = `${extensionPath}/catch/group/${configName}.json`;
    return path;
}

function getDetailConfigJsonPath(configName: string): string {
    let path = `${extensionPath}/catch/detail/${configName}.json`;
    return path;
}

function getConfigJson(configName: string): ICatchConfig {
    let path = getConfigJsonPath(configName);
    let jsonStr = fs.readFileSync(path, { encoding: 'utf-8' });
    let data = JSON.parse(jsonStr);
    return data;
}